#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "galois.h"
#include "galois_add.h"
#include <sys/time.h>

#define BUFSIZE (50000)
#define S_BUFSIZE (4096)
#define SPLIT 1
#define LAZY 2
#define COMP2 3

extern int printer;
int printer;

void usage(char *s)
{
	fprintf(stderr, "usage: gf_w64_tester split|lazy|comp2 ntrials-for-correctness\n");
	if (s != NULL) fprintf(stderr, "%s\n", s);
	exit(1);
}

main(int argc, char **argv)
{
	uint64_t w, alltype, nt, i, x, y, z, top, elt, q1, q2;
	uint64_t *b1, *b2;
	uint64_t *ib1, *ib2, *ib3;
	struct timeval t1, t2;
	struct timezone tz;
	long t0, now;
	double total, tsec;
	unsigned char *cb1, *cb2, *cb3;
	int mtype;

	if (argc != 3) usage(NULL);

	w = 64;

	/*  Table initialization */
	if (strcmp(argv[1], "split") == 0) {
		mtype = SPLIT;
		if (galois_create_split_w64() < 0) {
			fprintf(stderr, "error creating tables for split w64\n");
			exit(1);
		}
	} else if (strcmp(argv[1], "lazy") == 0) {
		mtype = LAZY;
	} else if (strcmp(argv[1], "comp2") == 0) {
		mtype = COMP2;
	} else {
		usage("arg 1 must be split\n");
	}

	nt = atoi(argv[2]);

	t0 = time(0);
	srand48(0); 
	printf("W: %d\n", w);
	printf("Method: %s\n", argv[1]);
	printf("Seeding random number generator with %lu\n", t0);

	top = (1 << w);

	b1 = (uint64_t *) malloc(sizeof(uint64_t) * S_BUFSIZE);
	b2 = (uint64_t *) malloc(sizeof(uint64_t) * S_BUFSIZE);
	for (i = 0; i < S_BUFSIZE; i++) {
		b1[i] = lrand48();
		b1[i] <<= 32;
		b1[i] += lrand48();
	}

	for (i = 0; i < S_BUFSIZE; i++) {
		b2[i] = lrand48();
		b2[i] <<= 32;
		b2[i] += lrand48();
	}

	cb1 = (unsigned char *) malloc(sizeof(unsigned char) * BUFSIZE);
	cb2 = (unsigned char *) malloc(sizeof(unsigned char) * BUFSIZE);
	cb3 = (unsigned char *) malloc(sizeof(unsigned char) * BUFSIZE);
	for (i = 0; i < BUFSIZE; i++) cb1[i] = lrand48() % 256;
	for (i = 0; i < BUFSIZE; i++) cb2[i] = lrand48() % 256;

	if (!(mtype == LAZY)) {
	printf("Doing %lu trials for single-operation correctness.\n", nt);
	for (i = 0; i < nt; i++) {
		x = lrand48();
		x <<= 32;
		x += lrand48();
		y = lrand48();
		y <<= 32;
		y += lrand48();
		switch (mtype) {
			case SPLIT: z = galois_split_multiply_w64(x, y); 
									q1 = galois_split_divide_w64(z, x); 
									q2 = galois_split_divide_w64(z, y); 
									break;
			case COMP2: z = galois_composite_multiply_k2(x, y, w);
									q1 = galois_composite_divide_k2(z, x, w);
									q2 = galois_composite_divide_k2(z, y, w);
		}
		if (q1 != y) {
			fprintf(stderr, "Failed test: %lu * %lu = %lu, but %lu / %lu = %lu\n",
					x, y, z, z, x, q1);
			exit(1);
		}
		if (q2 != x) {
			fprintf(stderr, "Failed test: %lu * %lu = %lu, but %lu / %lu = %lu\n",
					y, x, z, z, y, q2);
			exit(1);
		}
		switch (mtype) {
			case SPLIT:	z = galois_shift_inverse_w64(x);
									q1 = galois_shift_inverse_w64(z);
									break;
			case COMP2: z = galois_composite_inverse_k2(x, w);
									q1 = galois_composite_inverse_k2(z, w);
		}
		if (q1 != x) {
			fprintf(stderr, "Failed test: gf_inverse(%lu) = %lu, but gf_inverse(%lu) = %lu\n",
					x, z, z, q1);
			exit(1);
		}
	}
	printf("Passed Single-Operations Correctness Tests.\n");
	printf("\n");
	}

if (mtype == LAZY) {
		printf("Doing galois_w64_lazy_split_region_multiply correctness test.\n");
		ib1 = (uint64_t *) cb1;
		ib2 = (uint64_t *) cb2;
		ib3 = (uint64_t *) cb3;
		for (x = 0; x < 10; x++) {
			/* First, test with r2 specified, and add = 0 */
			elt = lrand48();
			elt <<= 32;
			elt += lrand48();
			galois_w64_lazy_split_region_multiply((unsigned char *) ib1, elt, BUFSIZE, (unsigned char *) ib3, 0);
			for (i = 0; i < BUFSIZE/8; i++) {
				printer = 1;
				if (galois_shift_multiply_w64(ib1[i], elt) != ib3[i]) {
					printf("Failed test (r2 != NULL, add == 0). Word %lu: %lu * %lu = %lu, but it is %lu in ib3\n",
							i, ib1[i], elt, galois_shift_multiply_w64(ib1[i], elt), ib3[i]);
					exit(1);
				}
			}

			/* Next, test with r2 = NULL */
			elt = lrand48();
			elt <<= 32;
			elt += lrand48();
			galois_w64_lazy_split_region_multiply((unsigned char *) ib1, elt, BUFSIZE, (unsigned char *) ib3, 0);
			elt = galois_shift_divide_w64(1, elt);
			galois_w64_lazy_split_region_multiply((unsigned char *) ib3, elt, BUFSIZE, NULL, 0);
			for (i = 0; i < BUFSIZE/8; i++) {
				if (ib1[i] != ib3[i]) {
					printf("Failed test (r2 == NULL): Byte %d (0x%lx): %lu != %lu\n", i, ib3+i, ib1[i], ib3[i]);
					exit(1);
				}
			}

			/* Finally, test with r2 specified, and add = 1 */

			elt = lrand48();
			elt <<= 32;
			elt += lrand48();
			for (i = 0; i < BUFSIZE/8; i++) ib3[i] = ib2[i];
			galois_w64_lazy_split_region_multiply((unsigned char *) ib1, elt, BUFSIZE, (unsigned char *) ib3, 1);
			for (i = 0; i < BUFSIZE/8; i++) {
				if (ib3[i] != (ib2[i] ^ galois_shift_multiply_w64(ib1[i], elt))) {
					printf("Failed test (r2 != NULL && add == 1) Byte %lu: (%lu * %lu) ^ %lu = %lu.  ib3 = %lu\n",
							i, ib1[i], elt, ib2[i], (galois_shift_multiply_w64(ib1[i], elt)^ib2[i]), ib3[i]);
					exit(1);
				}
			}
		}
		galois_w64_lazy_split_region_multiply((unsigned char *) ib1, 0, BUFSIZE, (unsigned char *) ib3, 0);
		for (i = 0; i < BUFSIZE/8; i++) {
			if (ib3[i] != 0) {
				printf("Failed multiply by zero test.  Byte %d = %d\n", i, ib3[i]);
				exit(1);
			}
		}
		printf("Passed galois_w64_lazy_split_region_multiply correctness test.\n");
		printf("\n");
	}

	if (mtype == SPLIT) {
		printf("Doing galois_w64_split_region_multiply correctness test.\n");
		ib1 = (uint64_t *) cb1;
		ib2 = (uint64_t *) cb2;
		ib3 = (uint64_t *) cb3;
		for (x = 0; x < 10; x++) {
			/* First, test with r2 specified, and add = 0 */
			elt = lrand48();
			elt <<= 32;
			elt += lrand48();
			galois_w64_split_region_multiply((unsigned char *) ib1, elt, BUFSIZE, (unsigned char *) ib3, 0);
			for (i = 0; i < BUFSIZE/8; i++) {
				printer = 1;
				if (galois_split_multiply_w64(ib1[i], elt) != ib3[i]) {
					printf("Failed test (r2 != NULL, add == 0). Word %lu: %lu * %lu = %lu, but it is %lu in ib3\n",
							i, ib1[i], elt, galois_split_multiply_w64(ib1[i], elt), ib3[i]);
					exit(1);
				}
			}

			/* Next, test with r2 = NULL */
			elt = lrand48();
			elt <<= 32;
			elt += lrand48();
			galois_w64_split_region_multiply((unsigned char *) ib1, elt, BUFSIZE, (unsigned char *) ib3, 0);
			elt = galois_split_divide_w64(1, elt);
			galois_w64_split_region_multiply((unsigned char *) ib3, elt, BUFSIZE, NULL, 0);
			for (i = 0; i < BUFSIZE/8; i++) {
				if (ib1[i] != ib3[i]) {
					printf("Failed test (r2 == NULL): Byte %d (0x%lx): %lu != %lu\n", i, ib3+i, ib1[i], ib3[i]);
					exit(1);
				}
			}

			/* Finally, test with r2 specified, and add = 1 */

			elt = lrand48();
			elt <<= 32;
			elt += lrand48();
			for (i = 0; i < BUFSIZE/8; i++) ib3[i] = ib2[i];
			galois_w64_split_region_multiply((unsigned char *) ib1, elt, BUFSIZE, (unsigned char *) ib3, 1);
			for (i = 0; i < BUFSIZE/8; i++) {
				if (ib3[i] != (ib2[i] ^ galois_split_multiply_w64(ib1[i], elt))) {
					printf("Failed test (r2 != NULL && add == 1) Byte %lu: (%lu * %lu) ^ %lu = %lu.  ib3 = %lu\n",
							i, ib1[i], elt, ib2[i], (galois_split_multiply_w64(ib1[i], elt)^ib2[i]), ib3[i]);
					exit(1);
				}
			}
		}
		galois_w64_split_region_multiply((unsigned char *) ib1, 0, BUFSIZE, (unsigned char *) ib3, 0);
		for (i = 0; i < BUFSIZE/8; i++) {
			if (ib3[i] != 0) {
				printf("Failed multiply by zero test.  Byte %d = %d\n", i, ib3[i]);
				exit(1);
			}
		}
		printf("Passed galois_w64_split_region_multiply correctness test.\n");
		printf("\n");
	}

	if (!(mtype == LAZY)) {
	printf("Speed Test #1: 10 Seconds of Multiply operations\n");
	t0 = time(0);
	gettimeofday(&t1, &tz);
	total = 0;
	while (time(0) - t0 < 10) {
		switch (mtype) {
			case SPLIT: for (i = 0; i < S_BUFSIZE; i++) galois_split_multiply_w64(b1[i], b2[i]); break;
		}
		total++;
	}
	gettimeofday(&t2, &tz);

	tsec = 0;
	tsec += t2.tv_usec;
	tsec -= t1.tv_usec;
	tsec /= 1000000.0;
	tsec += t2.tv_sec;
	tsec -= t1.tv_sec;
	total *= S_BUFSIZE;
	printf("Speed Test #1: %.5lf Mega Multiplies per second\n", total/tsec/1024.0/1024.0);

	printf("Speed Test #2: 10 Seconds of Divide operations\n");
	t0 = time(0);
	gettimeofday(&t1, &tz);
	total = 0;
	while (time(0) - t0 < 10) {
		switch (mtype) {
			case SPLIT: for (i = 0; i < S_BUFSIZE; i++) galois_split_divide_w64(b1[i], b2[i]); break;
		}
		total++;
	}
	gettimeofday(&t2, &tz);

	tsec = 0;
	tsec += t2.tv_usec;
	tsec -= t1.tv_usec;
	tsec /= 1000000.0;
	tsec += t2.tv_sec;
	tsec -= t1.tv_sec;
	total *= S_BUFSIZE;
	printf("Speed Test #2: %.5lf Mega Divides per second\n", total/tsec/1024.0/1024.0);
	}

	printf("\n");

	if (mtype == SPLIT) {
		printf("Doing 10 seconds worth of region_multiplies - Three tests:\n");
		printf("   Test 0: Overwrite initial region\n");
		printf("   Test 1: Products to new region\n");
		printf("   Test 2: XOR products into second region\n\n");

		for (i = 0; i < 3; i++) {
			t0 = time(0);
			gettimeofday(&t1, &tz);
			total = 0;
			while (time(0) - t0 < 10) {
				for (x = 0; x < 10; x++) {
					elt = lrand48();
					elt <<= 32;
					elt += lrand48();
					if (i == 0) {
						galois_w64_split_region_multiply((unsigned char *)ib1, elt, BUFSIZE, NULL, 0); 
					} else if (i == 1) {
						galois_w64_split_region_multiply((unsigned char *)ib1, elt, BUFSIZE, (unsigned char *)ib3, 0); 
					} else if (i == 2) {
						galois_w64_split_region_multiply((unsigned char *)ib1, elt, BUFSIZE, (unsigned char *)ib3, 1); 
					}
					total += BUFSIZE;
				}
			}
			gettimeofday(&t2, &tz);

			tsec = 0;
			tsec += t2.tv_usec;
			tsec -= t1.tv_usec;
			tsec /= 1000000.0;
			tsec += t2.tv_sec;
			tsec -= t1.tv_sec;
			printf("   Test %lu: %.5lf Megabytes of Multiplies per second\n", i, total/tsec/1024.0/1024.0);
		}
	}

	if (mtype == LAZY) {
		printf("Doing 10 seconds worth of region_multiplies - Three tests:\n");
		printf("   Test 0: Overwrite initial region\n");
		printf("   Test 1: Products to new region\n");
		printf("   Test 2: XOR products into second region\n\n");

		for (i = 0; i < 3; i++) {
			t0 = time(0);
			gettimeofday(&t1, &tz);
			total = 0;
			while (time(0) - t0 < 10) {
				for (x = 0; x < 10; x++) {
					elt = lrand48();
					elt <<= 32;
					elt += lrand48();
					if (i == 0) {
						galois_w64_lazy_split_region_multiply((unsigned char *)ib1, elt, BUFSIZE, NULL, 0); 
					} else if (i == 1) {
						galois_w64_lazy_split_region_multiply((unsigned char *)ib1, elt, BUFSIZE, (unsigned char *)ib3, 0); 
					} else if (i == 2) {
						galois_w64_lazy_split_region_multiply((unsigned char *)ib1, elt, BUFSIZE, (unsigned char *)ib3, 1); 
					}
					total += BUFSIZE;
				}
			}
			gettimeofday(&t2, &tz);

			tsec = 0;
			tsec += t2.tv_usec;
			tsec -= t1.tv_usec;
			tsec /= 1000000.0;
			tsec += t2.tv_sec;
			tsec -= t1.tv_sec;
			printf("   Test %lu: %.5lf Megabytes of Multiplies per second\n", i, total/tsec/1024.0/1024.0);
		}
	}

	return 0;
}
